/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright (c) 2008-2009, The KiWi Project (http://www.kiwi-project.eu)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 * - Redistributions of source code must retain the above copyright notice, 
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice, 
 *   this list of conditions and the following disclaimer in the documentation 
 *   and/or other materials provided with the distribution.
 * - Neither the name of the KiWi Project nor the names of its contributors 
 *   may be used to endorse or promote products derived from this software 
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * Contributor(s):
 * 
 * 
 */
package kiwi.core.services.importer.rdf;

import kiwi.core.api.config.ConfigurationService;
import kiwi.core.api.content.ContentItemService;
import kiwi.core.api.importer.Importer;
import kiwi.core.api.sesame.KiWiSesameService;
import kiwi.core.api.triplestore.LiteralService;
import kiwi.core.api.triplestore.ResourceService;
import kiwi.core.api.triplestore.TripleStore;
import kiwi.core.model.Constants;
import kiwi.core.model.rdf.*;
import kiwi.core.model.user.KiWiUser;
import org.openrdf.model.*;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.RepositoryResult;
import org.openrdf.repository.sail.SailRepository;
import org.openrdf.rio.RDFFormat;
import org.openrdf.rio.RDFParseException;
import org.openrdf.sail.memory.MemoryStore;
import org.openrdf.sail.nativerdf.NativeStore;
import org.slf4j.Logger;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.URL;
import java.util.*;

/**
 * An io for importing RDF sources in RDF/XML or other RDF formats. Currently uses
 * the Sesame parser for parsing the RDF content. 
 * <p>
 * For each triple found in the imported data, an appropriate KiWi triple is added. For each 
 * resource in the imported data, the io creates a ContentItem using rdfs:label or
 * dc:title as title and rdfs:comment or dc:description as content.
 * 
 * @author Sebastian Schaffert
 *
 */
public class RDFImporterImpl implements Importer {

    @Inject
    private Logger log;

    @Inject
    private TripleStore tripleStore;

    @Inject
    private ResourceService resourceService;

    @Inject
    private LiteralService literalService;

    @Inject
    private ConfigurationService configurationService;

    @Inject
    private ContentItemService contentItemService;


    @Inject
    private KiWiSesameService sesameService;



    private static String[] mime_types = {
        "application/rdf+xml",
        "text/n3",
        "application/turtle",
        "application/x-turtle"
    };

    /**
     * Get a collection of all mime types accepted by this io. Used for automatically
     * selecting the appropriate io in ImportService.
     *
     * @return a set of strings representing the mime types accepted by this io
     */
    @Override
    public Set<String> getAcceptTypes() {
        return new HashSet<String>(Arrays.asList(mime_types));
    }

    /**
     * Get a description of this io for presentation to the user.
     *
     * @return a string describing this io for the user
     */
    @Override
    public String getDescription() {
        return "Importer for various RDF formats (RDF/XML, N3, TURTLE); also supports OWL and RDFS files";
    }

    /**
     * Get the name of this io. Used for presentation to the user and for internal
     * identification.
     *
     * @return a string uniquely identifying this io
     */
    @Override
    public String getName() {
        return "RDF";
    }

    /**
     * Import data from the input stream provided as argument into the KiWi database.
     *
     * @param url the url from which to read the data
     * @param types the set of types to associate with each generated content item
     * @param tags the set of content items to use as tags
     * @param user the user to use as author of all imported data
     */
    @Override
    //@RaiseEvent("ontologyChanged")
    public int importData(URL url, String format, Set<KiWiUriResource> types, Set<KiWiResource> tags, KiWiUser user) {
        try {
            return importData(url.openStream(), format, types, tags, user);
        } catch (IOException ex) {
            log.error("I/O error while importing data from URL {}: {}", url, ex.getMessage());
            return 0;
        }
    }

    /**
     * Import data from the input stream provided as argument into the KiWi database.
     * <p>
     * Import function for formats supported by Sesame; imports the data first into a separate memory
     * repository, and then iterates over all statements, adding them to the current knowledge space.
     * This method also checks for resources that have a rdfs:label, dc:title, or skos:prefLabel and uses
     * it as the title for newly created ContentItems. 
     *
     * @param is the input stream from which to read the data
     * @param types the set of types to associate with each generated content item
     * @param tags the set of content items to use as tags
     * @param user the user to use as author of all imported data
     */
    @Override
    //@RaiseEvent("ontologyChanged")
    public int importData(InputStream is, String format, Set<KiWiUriResource> types, Set<KiWiResource> tags, KiWiUser user) {
        // TODO: need to figure out format automatically!
        RDFFormat f = getFormat(format);

        String baseUri = configurationService.getBaseUri();

        int count = 0;

        try {
            // File sesameDataDir = new
            // File(configurationService.getWorkDir()+File.separator+Long.toHexString(System.currentTimeMillis()));
            Repository myRepository = new SailRepository(new MemoryStore());

            myRepository.initialize();
            RepositoryConnection myCon = myRepository.getConnection();

            if (is != null) {

                long timer = System.currentTimeMillis();

                myCon.add(is, baseUri, f, (Resource) null); // no context (null)
                // for the moment,
                // clear it all

                log.info("imported data into temporary repository ({} ms)",
                        System.currentTimeMillis() - timer);

                count = importDataSesameInternal(myCon, types, tags, user);

            } else {
                log.error("could not load ontology; InputStream was null");
            }
            // myCon.clear();
            // myCon.clearNamespaces();
            myCon.close();
            myRepository.shutDown();
            // sesameDataDir.delete();
        } catch (RepositoryException ex) {
            log.error("error while importing Sesame data:", ex);
        } catch (RDFParseException ex) {
            log.error("parse error while importing Sesame data:", ex);
        } catch (IOException ex) {
            log.error("I/O error while importing Sesame data:", ex);
        }
        return count;

    }

    /**
     * Import data from the reader provided as argument into the KiWi database.
     * <p>
     * Import function for formats supported by Sesame; imports the data first into a separate memory
     * repository, and then iterates over all statements, adding them to the current knowledge space.
     * This method also checks for resources that have a rdfs:label, dc:title, or skos:prefLabel and uses
     * it as the title for newly created ContentItems. 
     *
     * @param reader the reader from which to read the data
     * @param types the set of types to associate with each generated content item
     * @param tags the set of content items to use as tags
     * @param user the user to use as author of all imported data
     */
    @Override
    //@RaiseEvent("ontologyChanged")
    public int importData(Reader reader, String format, Set<KiWiUriResource> types, Set<KiWiResource> tags, KiWiUser user) {

        // TODO: need to figure out format automatically!
        RDFFormat f = getFormat(format);

        String baseUri = configurationService.getBaseUri();

        int count = 0;

        try {
            File sesameDataDir = new File(configurationService.getWorkDir() + File.separator + Long.toHexString(System.currentTimeMillis()));
            Repository myRepository = new SailRepository(new NativeStore(sesameDataDir));

            myRepository.initialize();
            RepositoryConnection myCon = myRepository.getConnection();


            if (reader != null) {

                long timer = System.currentTimeMillis();

                myCon.add(reader, baseUri, f, (Resource) null);

                log.info("imported data into temporary repository ({} ms)", System.currentTimeMillis() - timer);

                count = importDataSesameInternal(myCon, types, tags, user);

            } else {
                log.error("could not load ontology; InputStream was null");
            }
            myCon.clear();
            myCon.clearNamespaces();
            myCon.close();
            myRepository.shutDown();
            sesameDataDir.delete();
        } catch (RepositoryException ex) {
            log.error("error while importing Sesame data:", ex);
        } catch (RDFParseException ex) {
            log.error("parse error while importing Sesame data:", ex);
        } catch (IOException ex) {
            log.error("I/O error while importing Sesame data:", ex);
        } catch (Exception ex) {
            log.error("unknown exception with message #0; skipping ontology", ex.getMessage());
        }
        return count;
    }


    @PostConstruct
    public void initialise() {
        log.info("registering RDF importer ...");

        //importService.registerImporter(this.getName(), "kiwi.service.io.rdf", this);
    }

    /**
     * internal generic method for importing from a temporary sesame connection
     * 16.09.2009: Changed from private to public scope (steffi)
     * @param myCon
     * @return
     * @throws RepositoryException
     */
    public int importDataSesame(RepositoryConnection myCon, Set<KiWiUriResource> types, Set<KiWiResource> tags, KiWiUser user) {
        try {
            return importDataSesameInternal(myCon, types, tags, user);
        } catch (RepositoryException ex) {
            log.error("error while importing Sesame data ({}); skipping ontology", ex.getMessage());
            return 0;
        } catch (Exception ex) {
            log.error("unknown exception with message {}; skipping ontology", ex.getMessage());
            return 0;
        }
    }

    /**
     * internal generic method for importing from a temporary sesame connection
     * 16.09.2009: Changed from private to public scope (steffi)
     * 07.12.2009: Changed back to private, because it otherwise causes exception intercepting by seam, which is
     *             not desirable
     * @param myCon
     * @return
     * @throws RepositoryException
     */
    private int importDataSesameInternal(RepositoryConnection myCon, Set<KiWiUriResource> types, Set<KiWiResource> tags, KiWiUser user) throws RepositoryException {
        int itemCount = 0;

        if (tags == null) {
            tags = new HashSet<KiWiResource>();
        }
        if (types == null) {
            types = new HashSet<KiWiUriResource>();
        }

        Locale defaultLocale = Locale.getDefault();
        long timer = System.currentTimeMillis();

        Set<KiWiResource> subjects = new HashSet<KiWiResource>();

        // import all triples
        Set<KiWiTriple> triples = sesameService.importFromSesame(myCon);
        for(KiWiTriple t : triples) {
            subjects.add(t.getSubject());
        }

        // import all namespaces
        for (RepositoryResult<Namespace> ns = myCon.getNamespaces(); ns.hasNext();) {
            Namespace ns1 = ns.next();

            tripleStore.setNamespace(ns1.getPrefix(), ns1.getName());
        }

        log.debug("transferred data into KiWi repository ({} ms)", System.currentTimeMillis() - timer);

        timer = System.currentTimeMillis();

        // look for rdfs:comment, rdfs:label, dc:title, dc:description, skos:prefLabel, skos:definition and store them
        // as kiwi:title and kiwi:hasTextContent also, so they will be used in page rendering
        URI type_xml = myCon.getValueFactory().createURI("http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral");

        HashMap<Locale,HashMap<Resource,String>> titles = new HashMap<Locale,HashMap<Resource,String>>();
        HashMap<Locale,HashMap<Resource,String>> descriptions = new HashMap<Locale,HashMap<Resource,String>>();

        URI rdfs_label      = myCon.getValueFactory().createURI(Constants.NS_RDFS+"label");
        URI rdfs_comment    = myCon.getValueFactory().createURI(Constants.NS_RDFS+"comment");

        URI skos_label      = myCon.getValueFactory().createURI(Constants.NS_SKOS+"prefLabel");
        URI skos_altlabel   = myCon.getValueFactory().createURI(Constants.NS_SKOS+"altLabel");
        URI skos_definition = myCon.getValueFactory().createURI(Constants.NS_SKOS+"definition");

        URI dc_title        = myCon.getValueFactory().createURI(Constants.NS_DC+"title");
        URI dc_description  = myCon.getValueFactory().createURI(Constants.NS_DC+"description");

        // precedence in increasing order: rdfs:label, dc:title, skos:altLabel, skos:prefLabel -
        for(URI prop : new URI[] { rdfs_label, dc_title, skos_altlabel, skos_label}) {

            for (RepositoryResult<Statement> stmts = myCon.getStatements(null, prop, null, false); stmts.hasNext();) {
                Statement stmt = stmts.next();

                Literal l = (Literal) stmt.getObject();

                Locale lang = l.getLanguage() == null ? Locale.getDefault() : new Locale(l.getLanguage());
                if(titles.get(lang) == null) {
                    titles.put(lang,new HashMap<Resource,String>());
                }
                HashMap<Resource,String> l_titles = titles.get(lang);

                l_titles.put(stmt.getSubject(), ((Literal) stmt.getObject()).stringValue());
            }
        }

        // precedence in increasing order: rdfs:comment, dc:description, skos:definition, dc:description, rdfs:comment
        for(URI prop : new URI[] { rdfs_comment, dc_description, skos_definition}) {
            for (RepositoryResult<Statement> stmts = myCon.getStatements(null, prop, null, false); stmts.hasNext();) {
                Statement stmt = stmts.next();

                Literal l = (Literal) stmt.getObject();

                Locale lang = l.getLanguage() == null ? Locale.getDefault() : new Locale(l.getLanguage());
                if(descriptions.get(lang) == null) {
                    descriptions.put(lang,new HashMap<Resource,String>());
                }
                HashMap<Resource,String> l_descriptions = descriptions.get(lang);


                if (type_xml.equals(l.getDatatype())) {
                    l_descriptions.put(stmt.getSubject(),"<div xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:kiwi=\"" + Constants.NS_KIWI_HTML + "\" kiwi:type=\"page\">" + l.stringValue() + "</p></div>");
                } else {
                    l_descriptions.put(stmt.getSubject(),"<div xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:kiwi=\"" + Constants.NS_KIWI_HTML + "\" kiwi:type=\"page\"><p>" + l.stringValue().replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;") + "</p></div>");
                }
            }
        }

        log.debug("identified relevant subject titles and descriptions ({} ms)", System.currentTimeMillis() - timer);

        timer = System.currentTimeMillis();


        KiWiUriResource kiwi_hasTextContent = resourceService.createUriResource(Constants.NS_KIWI_CORE+"hasTextContent");
        KiWiUriResource kiwi_title          = resourceService.createUriResource(Constants.NS_KIWI_CORE + "title");

        // now for each title and each description we set the KiWi 2 content item ...
        for(Locale lang : titles.keySet()) {
            HashMap<Resource,String> l_titles = titles.get(lang);

            for(Resource r : l_titles.keySet()) {
                if(r instanceof URI) {
                    KiWiUriResource kr = resourceService.createUriResource(r.stringValue());
                    KiWiLiteral kl = literalService.createLiteral(r.stringValue(),lang,Constants.NS_XSD+"string");
                    tripleStore.createTriple(kr,kiwi_title,kl,null);
                }
            }
        }
        for(Locale lang : descriptions.keySet()) {
            HashMap<Resource,String> l_descriptions = descriptions.get(lang);

            for(Resource r : l_descriptions.keySet()) {
                if(r instanceof URI) {
                    KiWiUriResource kr        = resourceService.createUriResource(r.stringValue());
                    KiWiTextContentLiteral kl = literalService.createTextContentLiteral(l_descriptions.get(r),lang);
                    tripleStore.createTriple(kr,kiwi_hasTextContent,kl,null);
                }
            }
        }


        // post processing for all subjects: set author, knowledge space, tags, types
//        for (KiWiResource tag : tags) {
//            for(KiWiResource subject : subjects) {
//                taggingService.createTagging(tag.getTitle(), subject, tag, user);
//            }
//        }

        // add parameter types as types
        for (KiWiUriResource type : types) {
            for(KiWiResource subject : subjects) {
                resourceService.addType(subject,type);
            }
        }


        // TODO: add knowledge space for this ontology to each resource; maybe this is even done in sesameService!

        itemCount = subjects.size();




        log.info("created {} ContentItems for resources with descripive information ({} ms)", itemCount, System.currentTimeMillis() - timer);
        return itemCount;
    }

    private static final RDFFormat getFormat(String format) {
        if ("application/rdf+xml".equals(format)) {
            return RDFFormat.RDFXML;
        } else if ("text/n3".equals(format)) {
            return RDFFormat.N3;
        } else if ("application/turtle".equals(format) || "application/x-turtle".equals(format)) {
            return RDFFormat.TURTLE;
        } else {
            return RDFFormat.RDFXML;
        }
    }
}
